﻿using Newtonsoft.Json;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace RedisNet
{
    public class RedisService
    {
        public readonly StackExchange.Redis.IDatabase _db;
        public RedisService(RedisClient client)
        {
            _db = client.GetDatabase();
        }

        #region Key 操作
        public async Task<bool> KeyExistsAsync(string key) => await _db.KeyExistsAsync(key);

        public async Task<bool> DeleteKeyAsync(string key) => await _db.KeyDeleteAsync(key);

        public async Task<long> DeleteKeysAsync(IEnumerable<string> keys) => await _db.KeyDeleteAsync(keys.Select(k => (RedisKey)k).ToArray());

        public async Task<bool> KeyRenameAsync(string key, string newKey) => await _db.KeyRenameAsync(key, newKey);

        public async Task<bool> SetExpirationAsync(string key, TimeSpan expiry) => await _db.KeyExpireAsync(key, expiry);
        #endregion

        #region String 操作
        public async Task<bool> StringSetAsync(string key, string value, TimeSpan? expiry = null) => await _db.StringSetAsync(key, value, expiry);

        public async Task<string> StringGetAsync(string key) => await _db.StringGetAsync(key);

        public async Task<long> StringAppendAsync(string key, string value) => await _db.StringAppendAsync(key, value);

        public async Task<bool> StringSetMultipleAsync(IDictionary<string, string> keyValues)
        {
            var pairs = keyValues.Select(kv => new KeyValuePair<RedisKey, RedisValue>(kv.Key, kv.Value)).ToArray();
            return await _db.StringSetAsync(pairs);
        }

        public async Task<IEnumerable<string>> StringGetMultipleAsync(IEnumerable<string> keys)
        {
            var redisKeys = keys.Select(k => (RedisKey)k).ToArray();
            return (await _db.StringGetAsync(redisKeys)).Select(v => (string)v);
        }
        #endregion

        #region Hash 操作
        public async Task<bool> HashSetFieldAsync(string key, string field, string value) => await _db.HashSetAsync(key, field, value);

        public async Task<string> HashGetFieldAsync(string key, string field) => await _db.HashGetAsync(key, field);

        public async Task<HashEntry[]> HashGetAllAsync(string key) => await _db.HashGetAllAsync(key);

        public async Task<bool> HashDeleteFieldAsync(string key, string field) => await _db.HashDeleteAsync(key, field);

        public async Task<long> HashDeleteFieldsAsync(string key, IEnumerable<string> fields) => await _db.HashDeleteAsync(key, fields.Select(f => (RedisValue)f).ToArray());

        public async Task HashSetObjectAsync<T>(string key, T obj) where T : class
        {
            var entries = ObjectToHashEntries(obj);
            await _db.HashSetAsync(key, entries);
        }

        public async Task<T> HashGetObjectAsync<T>(string key) where T : new()
        {
            var entries = await _db.HashGetAllAsync(key);
            return HashEntriesToObject<T>(entries);
        }
        #endregion

        #region List 操作
        public async Task<long> ListPushAsync<T>(string key, IEnumerable<T> items)
        {
            var values = items.Select(item => (RedisValue)JsonConvert.SerializeObject(item)).ToArray();
            return await _db.ListRightPushAsync(key, values);
        }

        public async Task<IEnumerable<T>> ListRangeAsync<T>(string key, long start = 0, long stop = -1)
        {
            var values = await _db.ListRangeAsync(key, start, stop);
            return values.Select(v => JsonConvert.DeserializeObject<T>(v));
        }

        public async Task<T> ListPopAsync<T>(string key)
        {
            var value = await _db.ListLeftPopAsync(key);
            return JsonConvert.DeserializeObject<T>(value);
        }
        #endregion

        #region 通用工具方法
        private HashEntry[] ObjectToHashEntries<T>(T obj) where T : class
        {
            return typeof(T).GetProperties()
                .Where(p => p.CanRead)
                .Select(p => new HashEntry(p.Name, p.GetValue(obj)?.ToString() ?? string.Empty))
                .ToArray();
        }

        private T HashEntriesToObject<T>(HashEntry[] entries) where T : new()
        {
            var obj = new T();
            var properties = typeof(T).GetProperties().Where(p => p.CanWrite);

            foreach (var prop in properties)
            {
                var entry = entries.FirstOrDefault(e => e.Name == prop.Name);
                if (entry.Value.HasValue)
                {
                    prop.SetValue(obj, Convert.ChangeType(entry.Value, prop.PropertyType));
                }
            }
            return obj;
        }
        #endregion

        #region 分布式系统相关
        /// <summary>
        /// 设置一个分布式锁，如果锁定成功，就去执行业务
        /// </summary>
        /// <param name="lockKey">标识一个特定锁，多个客户端可以尝试获取同一个 lockKey，但只有其中一个客户端能够成功获取锁</param>
        /// <param name="lockValue">标识获取锁的客户端（随机值），确保只有锁的持有者才能释放锁，从而避免误删其他客户端的锁</param>
        /// <param name="expiry"></param>
        /// <returns></returns>
        public async Task<bool> AcquireLockAsync(string lockKey, string lockValue, TimeSpan expiry)
        {
            return await _db.LockTakeAsync(lockKey, lockValue, expiry);
        }

        /// <summary>
        /// 释放获取的分布式锁
        /// </summary>
        /// <param name="lockKey"></param>
        /// <param name="lockValue"></param>
        /// <returns></returns>
        public async Task ReleaseLockAsync(string lockKey, string lockValue)
        {
            await _db.LockReleaseAsync(lockKey, lockValue);
        }

        /// <summary>
        /// 将指定键的值递增
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public async Task<long> IncrementAsync(string key, long value = 1) => await _db.StringIncrementAsync(key, value);

        /// <summary>
        /// 将指定键的值递减
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public async Task<long> DecrementAsync(string key, long value = 1) => await _db.StringDecrementAsync(key, value);
        #endregion
    }
}
